package zdream.pmfield.stage.director;

import java.util.Objects;
import java.util.Random;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.data.foe.MoveCheckPair;
import zdream.pmfield.data.foe.MoveTarget;
import zdream.pmfield.data.foe.MoveTargetType;
import zdream.pmfield.data.foe.MoveType;
import zdream.pmfield.data.foe.Type;
import zdream.pmfield.data.foe.VStat;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.action.DazeAction;
import zdream.pmfield.stage.action.StageAction;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.ActionRequestReply;
import zdream.pmfield.stage.message.FoeReceiveDamageMessage;
import zdream.pmfield.stage.message.RollReportMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.stage.Stage;
import zdream.pmfield.stage.object.Seat;
import zdream.pmfield.stage.object.StageMove;
import zdream.utils.MathUtils;

import static zdream.pmfield.stage.director.Director.ROLL_TITLE_ACTION;
import static zdream.pmfield.stage.director.HookType.*;

/**
 * 检定骰子 / 技能计算器
 *
 * @author Zdream
 * @date 2023-11-13
 * @since 0.0.1
 */
public class MoveChecker {
	public final Stage stage;
	public Random random = new Random();

	public MoveChecker(Stage stage) {
		this.stage = Objects.requireNonNull(stage);
	}

	/* ******************
	 * Action Validator *
	 ****************** */

	/**
	 * <p>检查提交的 ActionRequestReply
	 * <p>Note: 允许在 validate 时修改 reply.action
	 * </p>
	 */
	public Result validateActionRequestReply(ActionRequestMessage message, ActionRequestReply reply) {
		if (reply == null) {
			return Result.ofFailure("reply 为空");
		}
		if (message == null) {
			return Result.ofFailure("message 为空");
		}

		final StageAction action = reply.action;
		if (action == null) {
			return Result.ofFailure("action 为空");
		}

		if (action.isForAttack()) {
			return validateAttackAction(message, reply, action.asAttack());
		} else if (action.isForDaze()) {
			return Result.ofSuccess();
		}

		return Result.ofFailure("当前只允许 AttackAction / DazeAction");
	}

	public Result validateAttackAction(ActionRequestMessage message, ActionRequestReply reply, AttackAction action) {
		final FoeActor attacker = message.foe;

		if (action.foe != attacker) {
			return Result.ofFailure("action.foe 错误");
		}
		final StageMove move = action.move;
		if (move == null || move.foe != attacker) {
			return Result.ofFailure("action.move 错误");
		}

		if (action.move.move.id == Stage.SKIP_TURN_MOVE_ID) {
			reply.action = new DazeAction(attacker);
			return Result.ofSuccess();
		}
		if (action.move.move.id == Stage.MOVE_TO_MOVE_ID && !message.movable) {
			return Result.ofFailure("action 无法移动");
		}

		if (!attacker.hasEnoughActionPointsForMove(move)) {
			return Result.ofFailure("action 没有足够的行动力");
		}

		return checkAttackAction(action);
	}

	/**
	 * 调到这里的时候, 行动力已经检查过或者扣除了
	 */
	public Result checkAttackAction(AttackAction action) {
		final FoeActor attacker = action.foe;
		final StageMove move = action.move;

		final MoveTarget moveTarget = move.target;
		if (moveTarget.type == MoveTargetType.SELF) {
			action.targetFoe = attacker;
			action.targetSeat = null;
			return Result.ofSuccess();
		}

		if (moveTarget.type == MoveTargetType.ENEMY) {
			return checkEnemyMoveTarget(action);
		}
		if (moveTarget.type == MoveTargetType.SEAT) {
			return checkSeatMoveTarget(action);
		}

		return Result.ofFailure("未知的 move.target: " + moveTarget);
	}

	public Result checkEnemyMoveTarget(AttackAction action) {
		if (action.targetFoe == null) {
			return Result.ofFailure("action.targetFoe 为空");
		}

		action.targetSeat = action.targetFoe.seat;

		// 检查发动距离
		if (stage.enableDistanceCheck) {
			Seat currentSeat = action.foe.seat;
			int distance = currentSeat.distance(action.targetSeat);
			if (!action.move.target.isValidDistance(distance)) {
				return Result.ofFailure("action.targetSeat 不在技能的发动范围内");
			}
		}

		// TODO 其它检查, 比如查看技能释放有没有被阻挡, 要用到 onHook

		return Result.ofSuccess();
	}

	public Result checkSeatMoveTarget(AttackAction action) {
		if (action.targetFoe != null) {
			// 全部转成 targetSeat
			action.targetSeat = action.targetFoe.seat;
			action.targetFoe = null;
		} else if (action.targetSeat == null) {
			return Result.ofFailure("action.targetSeat 为空");
		}

		if (action.targetSeat.stage != stage) {
			return Result.ofFailure("action.targetSeat 错误");
		}

		// 检查发动距离
		Seat currentSeat = action.foe.seat;
		int distance = currentSeat.distance(action.targetSeat);
		if (!action.move.target.isValidDistance(distance)) {
			return Result.ofFailure("action.targetSeat 不在技能的发动范围内");
		}

		// TODO 其它检查, 比如查看技能释放有没有被阻挡, 要用到 onHook

		return Result.ofSuccess();
	}

	/* **********
	 *   Roll   *
	 ********** */

	public Roll[] createWithMove(StageMove move) {
		final VStat[] vStats = move.vStats;

		final Roll[] rolls = new Roll[vStats.length];
		final int checkRateFixed = move.checkRateFixed;

		for (int i = 0; i < vStats.length; i++) {
			VStat vStat = vStats[i];
			rolls[i] = createOne(move.foe, vStat, checkRateFixed);
		}

		return rolls;
	}

	/**
	 * 一般在创建 {@link AttackContext#moreRolls} 时才调用这个
	 */
	public Roll createMoreWithMove(StageMove move, int index) {
		final VStat vStat = move.vStats[index % move.vStats.length];
		return createOne(move.foe, vStat, move.checkRateFixed);
	}

	public Roll createOne(FoeActor foe, VStat vStat) {
		return createOne(foe, vStat, 0);
	}

	public Roll createOne(FoeActor foe, VStat vStat, int checkRateFixed) {
		final Roll roll = createRoll();
		roll.vStat = vStat;
		roll.baseValue = foe.vStats[vStat.ordinal()];
		roll.baseValue += checkRateFixed;
		roll.fixedBaseValue = roll.baseValue;
		roll.refresh();
		return roll;
	}

	protected Roll createRoll() {
		return new Roll((short) random.nextInt(100));
	}

	public int randomInt(int bound) {
		return this.random.nextInt(bound);
	}

	/* **************
	 * Move Release *
	 ************** */

	public void deductActionPoints(AttackContext context) {
		final StageMove move = context.action.move;
		final FoeActor attacker = context.attacker;

		context.actionPoints = attacker.getActionPointsCost(move);
		context.movePoints = attacker.getMovePointsCost(move);

		if (context.isAction()) {
			attacker.deductActionPoints(move);
		}
	}

	/**
	 * 希望这个方法执行过程中, 不修改 {@link FoeActor} 里面的数据, 只修改 {@link AttackContext}
	 */
	public void evaluateAttack(AttackContext context) {
		final StageMove move = context.currentMove;
		final FoeActor attacker = context.attacker;

		// 1. 有些攻击是多体, 锁定攻击范围
		final Result result = checkAttackAction(context.action);
		if (!result.success) {
			context.abort = true;
			context.abortReason = result.errorMessage;
			if (!context.skipValidation) {
				return;
			}
		}
		this.checkAffectedSeats(context);

		// 2. 判定骰子
		// 判定需要在确定攻击范围后面, 来确定攻击时的准确度衰减
		if (context.isAction()) {
			context.checkPair = determineMoveLevel(context);
			if (context.rolls.length > 0) {
				stage.pushMessage(new RollReportMessage(attacker, context.rolls, context.moreRolls)
						.withTitle(ROLL_TITLE_ACTION));
			}
			attacker.putCheckPairToMoveExecutingBuff(context.checkPair);
		} else if (context.isDryRun()) {
			Objects.requireNonNull(context.checkPair);
		} else {
			throw new IllegalStateException("invalid title: " + context.title);
		}
		context.power = context.checkPair.power;

		// 3. 确定属性点与加成 (攻击方)
		if (context.skipCalcAttack) {
			if (context.attack == 0) {
				throw new IllegalStateException("You should set context.attack when skipCalcAttack is enabled.");
			}
		} else {
			context.attack = attacker.getBStat(move.attackerUsing == MoveType.PHYSICAL ? BStat.AT : BStat.SA);
		}

		for (AttackContext.AttackContextItem item : context.items) {
			item.miss = false;

			// 4. 确定属性点 (防御方)
			FoeActor targetFoe = item.targetFoe;
			if (targetFoe == null) {
				item.defense = 1;
				continue;
			}

			item.defense = item.targetFoe.bStats[
					move.attackerUsing == MoveType.PHYSICAL ? BStat.DF.ordinal() : BStat.SD.ordinal()];

			// 5. 确定加成
			for (Type.TypeWithWeight type : move.types) {
				for (Type targetType : targetFoe.findTypes()) {
					item.typeFixPairs.add(FixPair.of(type, targetType));
				}
			}
			item.defenseFixed = item.powerFixed = 1f;
		}

		// 在这个 hook 可以修改 type fix rate / attack / power / defense
		Hooks.AttackHookContext hooks = attacker.triggerHook(ON_CHECK_TYPE_FIXED).as();
		context.items.stream()
				.filter(item -> item.targetFoe != null)
				.forEach(item -> hooks.append(item.targetFoe));
		hooks.withAttackContext(context).run();

		for (AttackContext.AttackContextItem item : context.items) {
			if (item.typeFixPairs.isEmpty()) {
				continue;
			}
			item.typeFixed = FixPair.fixedRateTotal(item.typeFixPairs);

			// 6. 伤害计算 (但不实施)
			if (context.power == 0 || item.typeFixed == 0) {
				continue;
			}
			item.damage = item.miss ? 0 : Damages.damage(item);
		}
	}

	/**
	 * 是 dry run 的不要调这个
	 */
	public void executeAttackDamage(AttackContext context) {
		if (!context.isAction()) {
			throw new IllegalStateException("You can execute damage only when context is for action");
		}

		final StageMove move = context.currentMove;
		boolean attackMove =
				move.attackerUsing == MoveType.PHYSICAL ||
				move.attackerUsing == MoveType.SPECIAL;

		if (!attackMove) {
			return;
		}

		for (AttackContext.AttackContextItem item : context.items) {
			final FoeActor targetFoe = item.targetFoe;
			if (targetFoe == null) {
				continue;
			}

			final FoeReceiveDamageMessage damageMessage = new FoeReceiveDamageMessage(targetFoe)
					.withTitle(ROLL_TITLE_ACTION)
					.withDamage(item.damage)
					.withHpFrom(targetFoe);

			if (item.damage > 0) {
				targetFoe.receiveDamage(item.damage);
			}

			damageMessage.withHpTo(targetFoe);
			if (item.damage > 0) {
				stage.pushMessage(damageMessage);
			}

			context.report.createDamageReport(targetFoe)
					.withMiss(item.miss)
					.withDamage(item.damage)
					.withHpChange(damageMessage.hpFrom, damageMessage.hpTo)
					.withShieldChange(damageMessage.shieldFrom, damageMessage.shieldTo)
					.withTypeFixed(item.typeFixed)
					.withEffectFixedRate(context.effectFixed, item.defenseFixed, item.powerFixed);
		}
	}

	/**
	 * 计算投点的概率, 计算每个投点的概率
	 * @return 数组, 比如投出 1 个点的概率就是 [1]. (100% 是 1)
	 */
	public float[] calculateLevelProbability(AttackContext context) {
		if (!context.isForAccuracyChecking()) {
			throw new IllegalArgumentException("You can execute damage only when context is accuracy checking");
		}

		// 1. 有些攻击是多体, 锁定攻击范围
		this.checkAffectedSeats(context);

		// 2. 判定骰子
		determineMoveLevel(context);

		// 3. 计算概率
		int maxLevel = context.rolls.length + context.moreRolls.length;
		float[] rollRates = new float[maxLevel];

		for (int i = 0; i < context.rolls.length; i++) {
			Roll roll = context.rolls[i];
			rollRates[i] = roll.affectedBy == null ?
					roll.fixedBaseValue / 100f :
					roll.affectResult ? 1 : 0;
		}
		for (int i = 0; i < context.moreRolls.length; i++) {
			Roll roll = context.moreRolls[i];
			rollRates[i + context.rolls.length] = roll.affectedBy == null ?
					roll.fixedBaseValue / 100f :
					roll.affectResult ? 1 : 0;
		}

		for (int i = 0; i < rollRates.length; i++) {
			float rate = rollRates[i];
			if (rate > 1) {
				rollRates[i] = 1;
			} else if (rate < 0) {
				rollRates[i] = 0;
			}
		}

		return MathUtils.probabilityOfCompositeRates(rollRates);
	}

	public void checkAffectedSeats(AttackContext context) {
		// 途径
		int fromX = context.attacker.seat.x;
		final AttackAction action = context.action;
		if (action.targetSeat != null) {
			int toX = action.targetSeat.x;
			int delta = fromX > toX ? -1 : 1;

			for (int x = fromX; x <= toX; x += delta) {
				context.routes.add(Objects.requireNonNull(stage.findSeatByX(x)));
			}
		}

		// 受影响的 seats
		// 先不考虑溅射效果 TODO
		if (action.targetFoe != null) {
			context.createItemForFoe(action.targetFoe);
		} else if (action.targetSeat != null) {
			context.createItemForSeat(action.targetSeat);
		}
	}

	/**
	 * 看看检定的结果是大成功还是失败
	 */
	public MoveCheckPair determineMoveLevel(AttackContext context) {
		StageMove move = context.currentMove;
		final FoeActor foe = context.attacker;

		final Roll[] rolls = context.rolls = createWithMove(move);
		final Hooks.AttackHookContext hooks = foe.triggerHook(HookType.BEFORE_ATTACK_ROLL_CHECK).as();
		if (context.action.targetFoe != null) {
			hooks.append(context.action.targetFoe);
		}
		hooks.appendAll(context.routes).append(stage.globalSeat);
		hooks.withAttackContext(context).run();

		int level = 0;
		for (Roll roll : rolls) {
			roll.refresh();

			if (roll.result) {
				level++;
			}
		}

		for (Roll roll : context.moreRolls) {
			roll.refresh();

			if (roll.result) {
				level++;
			}
		}

		return getCheckPairByLevel(move, level);
	}

	public MoveCheckPair getCheckPairByLevel(StageMove move, int level) {
		final MoveCheckPair pair = move.pairs.get((byte) level);
		return pair == null ? createCheckPairForLargerLevel(move, level) : pair;
	}

	private MoveCheckPair createCheckPairForLargerLevel(StageMove move, int level) {
		if (level <= move.vStats.length) {
			throw new RuntimeException("技能 " + move + " 的判定等级 " + level + " 没写好");
		}

		MoveCheckPair basePair = getCheckPairByLevel(move, move.vStats.length).copy();
		basePair.level = (byte) level;

		MoveCheckPair more = move.morePair;
		if (more == null) {
			return basePair;
		}

		for (int i = move.vStats.length + 1; i <= level; i++) {
			basePair.power += more.power;
		}
		return basePair;
	}
}
